﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://wpfimageviewer.codeplex.com
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows.Media.Imaging;
using AForge.Imaging;
using AForge.Imaging.Filters;
using BrainTechLLC;
using BrainTechLLC.WPF;
using BrainTechLLC.ThreadSafeObjects;
using BrainTechLLC.ImageOperators;
using BrainTechLLC.ImageOperatorsNonAForgeDependent;


namespace BrainTechLLC.FilterExtensions
{
    public static class DetermineSimilarity
    {
        public static FilterCollection _similarityFilterCollection;

        public static RGBAndCoordinate[,] CreateCoordinateMatrix(this List<RGBAndCoordinate> list, int width, int height)
        {
            RGBAndCoordinate[,] result = new RGBAndCoordinate[width + 1, height + 1];
            
            foreach (var item in list)
            {     
                result[item.X, item.Y] = item;
            }

            return result;
        }

        public static bool IsValidSimilarityMeasure(this double d)
        {
            return (d != double.MaxValue && !double.IsNaN(d));
        }

        public static double GetScaleFactor(int width, int height)
        {
            return Scaling.GetScaleFactor(width, height, Settings.Values.MinScaleWidth, Settings.Values.MinScaleHeight, Settings.Values.ScaleFactor);
        }

        public static double Similarity(this Bitmap bm1, Bitmap bm2, string fileName1, string fileName2, int maxWidth, int maxHeight, double aspect1, double aspect2, bool makeCopies)
        {
            if (bm1 == null || bm2 == null)
                return double.MaxValue;

            if (bm1 == bm2)
                return 0D;

            int wid1 = bm1.Width;
            int hei1 = bm1.Height;
            int wid2 = bm2.Width;
            int hei2 = bm2.Height;
            double scale1 = GetScaleFactor(wid1, hei1);
            double scale2 = GetScaleFactor(wid2, hei2);
            double scale = Math.Max(scale1, scale2);
            return Similarity(bm1, bm2, fileName1, fileName2, maxWidth, maxHeight, aspect1, aspect2, scale, makeCopies);
        }

        public static double PrepImages(this Bitmap bmp1, Bitmap bmp2, string fileName1, string fileName2, int maxWidth, int maxHeight, double aspect1, double aspect2, double scaleFactor, FilterCollection fc, bool makeCopies, bool wantPostProcessInfo, out Bitmap bm1Final, out Bitmap bm2Final, out List<RGBAndCoordinate> postProcessInfo1, out List<RGBAndCoordinate> postProcessInfo2, out int width, out int height)
        {
            width = 0; height = 0;
            bm1Final = null; bm2Final = null;
            postProcessInfo1 = null; postProcessInfo2 = null;

            if (bmp1 == null || bmp2 == null)
                return double.MaxValue;

            if (bmp1 == bmp2)
                return 0D;

            int width1 = bmp1.Width;
            int height1 = bmp1.Height;
            int width2 = bmp2.Width;
            int height2 = bmp2.Height;
            int biggerWidth = width1 > width2 ? width1 : width2;
            int biggerHeight = height1 > height2 ? height1 : height2;

            if (!aspect1.WithinBounds(aspect2 - (aspect2 * 0.05D), aspect2 + (aspect2 * 0.05D)))
                return double.MaxValue;

            double scale1 = Scaling.GetScaledImageDimensions((double)width1, (double)height1, (double)maxWidth, (double)maxHeight);
            int newWidth1 = (int)Math.Floor((double)width1 * scale1);
            int newHeight1 = (int)Math.Floor((double)height1 * scale1);

            double scale2 = Scaling.GetScaledImageDimensions((double)width2, (double)height2, (double)maxWidth, (double)maxHeight);
            int newWidth2 = (int)Math.Floor((double)width2 * scale2);
            int newHeight2 = (int)Math.Floor((double)height2 * scale2);

            var cached1 = SimilarityQueueAndCache.SimilarityCache[fileName1];
            var cached2 = SimilarityQueueAndCache.SimilarityCache[fileName2];

            if (cached1 != null)
            {
                bm1Final = cached1.Bitmap;
                postProcessInfo1 = cached1.PostProcessInfo;
                width = cached1.Width;
                height = cached1.Height;
            }

            if (cached2 != null)
            {
                bm2Final = cached2.Bitmap;
                postProcessInfo2 = cached2.PostProcessInfo;
                width = cached2.Width;
                height = cached2.Height;
            }

            if (bm1Final == null)
            {
                try
                {
                    if (newWidth1 < width1 || newHeight1 < height1)
                    {
                        // Optimize
                        var renderSettings = Settings.Values.StandardRenderSettings;
                        bmp1 = bmp1.CreateBitmap(newWidth1, newHeight1, renderSettings);
                    }


                    bm1Final = bmp1.GetPreparedBitmap(fc, wantPostProcessInfo, out postProcessInfo1, out width, out height);
                    var cacheEntry = new SimilarityCacheEntry() { FileName = fileName1, Bitmap = bm1Final, PostProcessInfo = postProcessInfo1, Width = width, Height = height };
                    if (SimilarityQueueAndCache.SimilarityCache.Add(fileName1, cacheEntry))
                    {
                        SimilarityQueueAndCache.SimilarityQueue.Enqueue(cacheEntry);
                    }
                }
                catch { return double.MaxValue; }
            }

            if (bm2Final == null)
            {
                try
                {
                    if (newWidth2 < width2 || newHeight2 < height2)
                    {
                        var renderSettings = Settings.Values.StandardRenderSettings;
                        bmp2 = bmp2.CreateBitmap(newWidth2, newHeight2, renderSettings);
                    }

                    bm2Final = bmp2.GetPreparedBitmap(fc, wantPostProcessInfo, out postProcessInfo2, out width, out height);
                    var cacheEntry = new SimilarityCacheEntry() { FileName = fileName2, Bitmap = bm2Final, PostProcessInfo = postProcessInfo2, Width = width, Height = height };
                    if (SimilarityQueueAndCache.SimilarityCache.Add(fileName2, cacheEntry))
                    {
                        SimilarityQueueAndCache.SimilarityQueue.Enqueue(cacheEntry);
                    }
                }
                catch { return double.MaxValue; }
            }

            return 0D;
        }

	
        public static double Similarity(this Bitmap bm1, Bitmap bm2, string fileName1, string fileName2, int maxWidth, int maxHeight, double aspect1, double aspect2, double scaleFactor, bool makeCopies)
        {
            Bitmap bm1Final;
            Bitmap bm2Final;
            int width; int height;
            List<RGBAndCoordinate> postProcessInfo1;
            List<RGBAndCoordinate> postProcessInfo2;

            if (bm1 == null || bm2 == null)
                return double.MaxValue;

            if (bm1 == bm2)
                return 0D;

            try
            {
                double val = PrepImages(bm1, bm2, fileName1, fileName2, maxWidth, maxHeight, aspect1, aspect2, scaleFactor, DetermineSimilarity._similarityFilterCollection, makeCopies, true, out bm1Final, out bm2Final, out postProcessInfo1, out postProcessInfo2, out width, out height);

                if (val == double.MaxValue)
                    return double.MaxValue;

                Average average = GetMoveAndCompareAverage(width, height, postProcessInfo1, postProcessInfo2);             
                return (double)average.Min;
            }
            catch (Exception ex)
            {                
                Debug.WriteLine(ex.ToString());
                Debug.Assert(false);
                return double.MaxValue;
            }
        }

        private static Average GetMoveAndCompareAverage(int width, int height, List<RGBAndCoordinate> postProcessInfo1, List<RGBAndCoordinate> postProcessInfo2)
        {
            Average average = new Average();

            if (postProcessInfo1 != null && postProcessInfo2 != null)
            {
                var matrix1 = CoordMatrix.CreateCoordinateMatrix(postProcessInfo1, width, height);
                var matrix2 = CoordMatrix.CreateCoordinateMatrix(postProcessInfo2, width, height);
                var diff = CoordMatrix.CompareCoordinateMatrixAndSumDifference(matrix1, matrix2, width, height);
                var doubleVal = Convert.ToDouble(diff) / Convert.ToDouble(width * height);
                average.AddValue(doubleVal);

                for (int amount = 1; amount < 4; amount++)
                {
                    Direction d = Direction.Up;
                    doubleVal = MoveAndCompare(width, height, matrix1, matrix2, amount, d);
                    average.AddValue(doubleVal);

                    d = Direction.Down;
                    doubleVal = MoveAndCompare(width, height, matrix1, matrix2, amount, d);
                    average.AddValue(doubleVal);

                    d = Direction.Left;
                    doubleVal = MoveAndCompare(width, height, matrix1, matrix2, amount, d);
                    average.AddValue(doubleVal);

                    d = Direction.Right;
                    doubleVal = MoveAndCompare(width, height, matrix1, matrix2, amount, d);
                    average.AddValue(doubleVal);

                    d = Direction.DownLeft;
                    doubleVal = MoveAndCompare(width, height, matrix1, matrix2, amount, d);
                    average.AddValue(doubleVal);

                    d = Direction.DownRight;
                    doubleVal = MoveAndCompare(width, height, matrix1, matrix2, amount, d);
                    average.AddValue(doubleVal);

                    d = Direction.UpLeft;
                    doubleVal = MoveAndCompare(width, height, matrix1, matrix2, amount, d);
                    average.AddValue(doubleVal);

                    d = Direction.UpRight;
                    doubleVal = MoveAndCompare(width, height, matrix1, matrix2, amount, d);
                    average.AddValue(doubleVal);
                }
            }
            return average;
        }

        private static double MoveAndCompare(int width, int height, CoordMatrix matrix1, CoordMatrix matrix2, int amount, Direction d)
        {
            int offsetX, offsetY;
            d.GetXYOffset(out offsetX, out offsetY);

            var newMatrix2 = CoordMatrix.MoveCoordinateMatrix(matrix2, width, height, offsetX, offsetY, amount);
            var diff = CoordMatrix.CompareCoordinateMatrixAndSumDifference(matrix1, newMatrix2, width, height);
            var doubleVal = Convert.ToDouble(diff) / Convert.ToDouble(width * height);
            return doubleVal;
        }
    }
}
